First Delivery
A complete walkthrough of the delivery chain, end to end: source → build → workload → runtime → traffic → TLS.
We'll deliver the same example application used throughout these docs — for-kaniko-app, built with the kaniko Shipwright strategy.
Every resource below is namespaced and belongs to one Environment, which owns them all via ownerReference.
0. Namespace and Secrets
kubectl create namespace dev
You will not kubectl create secret anything for this walkthrough. Build's Git clone key and registry push credential are pulled automatically by the controller via ExternalSecret, from whichever backend the Environment's contract.secretStore.provider points at (step 1 below).
That does assume a ClusterSecretStore named blanketops-environments-aws (or -vault/-gcp/-azure) already exists on the cluster, pointed at your actual secret manager — that's cluster infrastructure, not something this walkthrough or the Environment controller creates.
These remote keys need a value in that store before step 1, not before step 4 — Environment is what resolves contract.secretStore.provider, and it mediates Build (and everything else composed into it) on the assumption its credentials already exist. Applying the Environment before the store is populated doesn't fail loudly on Environment itself; it surfaces later as Build's mediation failing to find its ExternalSecret data:
| Remote key | Backs |
|---|---|
/blanketops/git/ssh-privatekey | Git SSH clone key (ssh-privatekey) |
/blanketops/git/ssh-publickey | Git SSH clone key (ssh-publickey) |
/blanketops/git/known-hosts | Git SSH clone key (known_hosts) |
/blanketops/registry/config | Registry push credential (.dockerconfigjson) |
These paths are platform constants, not something you name yourself. Full detail, plus the exact aws secretsmanager create-secret commands to populate them: Environment: Secrets & SecretStore.
1. Environment
The envelope for everything that follows.
apiVersion: environments.blanketops.dev/v1alpha1
kind: Environment
metadata:
name: for-kaniko-app-main
namespace: dev
spec:
contract:
applicationName: for-kaniko-app
branch: main
gitOwner: example-org
environmentType: development
version: v0.1.0
gitRepository:
name: for-kaniko-app
gitHubEvent:
name: for-kaniko-app-3f2c91d
build:
name: build-sample-kaniko
serviceUnits:
- name: for-kaniko-app-api
deployment:
name: for-kaniko-app
route:
name: route-sample
domain:
name: for-kaniko-app-domain
contract:
secretStore:
provider: aws
contract.secretStore.provider is what makes the ExternalSecrets in step 0 resolvable — set it to whichever backend you actually populated (aws, vault, gcp, or azure).
kubectl apply -f environment.yaml
Its phase stays Pending until every resource it references below exists and is ready.
2. GitRepository
Registers the source origin.
apiVersion: sources.blanketops.dev/v1alpha1
kind: GitRepository
metadata:
name: for-kaniko-app
namespace: dev
spec:
contract:
provider: github
hookUrl: https://your-webhook-endpoint.example.com/
repository:
owner: example-org
name: for-kaniko-app
webhooks:
events:
- push
- pull_request
kubectl apply -f gitrepository.yaml
3. GitHubEvent
In production, a real webhook delivery creates this automatically. To drive the chain manually for this walkthrough, create one directly — the same manual-dispatch path the platform itself uses for non-webhook triggers.
apiVersion: events.blanketops.dev/v1alpha1
kind: GitHubEvent
metadata:
name: for-kaniko-app-3f2c91d
namespace: dev
spec:
contract:
repository: example-org/for-kaniko-app
eventType: push
ref: refs/heads/main
commitSHA: 3f2c91d
kubectl apply -f githubevent.yaml
4. Build
Declares the deterministic transformation from source to image.
apiVersion: environments.blanketops.dev/v1alpha1
kind: Build
metadata:
name: build-sample-kaniko
namespace: dev
spec:
contract:
image: docker.io/example/for-kaniko-app:main
strategy:
kind: ClusterBuildStrategy
name: kaniko
source:
url: git@github.com:example-org/for-kaniko-app.git
revision: main
contextDir: .
cloneSecret: git-ssh-credentials
serviceAccount:
name: build-bot
secret: registry-credentials
policy:
triggers:
- type: push
- type: pull_request
kubectl apply -f build.yaml
cloneSecret: git-ssh-credentials and serviceAccount.secret: registry-credentials are names you choose — the Build controller creates an ExternalSecret under each name, pulling from the fixed remote keys listed in step 0. You never create these Secrets yourself.
5. ServiceUnit
Turns the built image into a workload contract.
apiVersion: environments.blanketops.dev/v1alpha1
kind: ServiceUnit
metadata:
name: for-kaniko-app-api
namespace: dev
spec:
contract:
type: build
buildRef:
name: build-sample-kaniko
containerPort: 8080
size: 2
appType: web
stackType: nodejs
kubectl apply -f serviceunit.yaml
6. Deployment
Projects the ServiceUnit into runtime.
apiVersion: environments.blanketops.dev/v1alpha1
kind: Deployment
metadata:
name: for-kaniko-app
namespace: dev
spec:
contract:
serviceUnits:
- for-kaniko-app-api
runtime: kubernetes.io/container-runtime
strategy: Rolling
imageAutomation: false
kubectl apply -f deployment.yaml
7. Route
Exposes the deployed workload.
apiVersion: networks.blanketops.dev/v1alpha1
kind: Route
metadata:
name: route-sample
namespace: dev
spec:
contract:
host: api.dev.example.com
path: /
enabled: true
tlsEnabled: true
runtime: kubernetes.io/container-runtime
serviceUnitRef:
name: for-kaniko-app-api
kubectl apply -f route.yaml
8. Domain
Because tlsEnabled: true above, the Route needs an owned Domain to govern its certificate. Reference the Route you just created:
apiVersion: networks.blanketops.dev/v1alpha1
kind: Domain
metadata:
name: for-kaniko-app-domain
namespace: dev
spec:
contract:
host: api.dev.example.com
routeRef:
name: route-sample
tlsStrategy: platform
kubectl apply -f domain.yaml
What Just Happened
Nine resources, one Environment, one cascading delete boundary. From here:
Environment → GitRepository → GitHubEvent → Build → ServiceUnit → Deployment → Route → Domain
Each is reconciled independently, and the Environment's status.phase aggregates all of them into one signal.
Continue to Verify to check each stage's status.